﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace SimpleIR
{
	/// <summary>
	/// This class is used to represent all bytecode operations load a value onto the eval stack in an indirect way (field derefernece, pointer loads, etc.).
	/// </summary>
	public sealed class IRLoadToEvalStackIndirect : IRByteCode
	{
		/// <summary>
		/// An array containing all the acceptable opcodes for this operation class (in order)
		/// </summary>
		private static readonly OpCode.Op[] AcceptableOps = { OpCode.Op.ArrayLoad, OpCode.Op.ArrayLength, OpCode.Op.IndirectRead, OpCode.Op.LoadStructField, OpCode.Op.LoadMemberField };

		/// <summary>
		/// This is the type that we are loading from if this is an array load.
		/// </summary>
		private readonly IRArrayType m_arraytype;

		/// <summary>
		/// This is the type of the pointer we are going to read through.
		/// </summary>
		private readonly IRReferenceType m_reftype;

		/// <summary>
		/// This is the member field that we we are going to read from in a struct field load op.
		/// </summary>
		private readonly IRMemberField m_readfield;

		private IRLoadToEvalStackIndirect(OpCode.Op opc, IRArrayType arraytype, IRReferenceType readthroughtype, IRMemberField readfield, IRSourceLocation sloc)
			: base(opc, sloc)
		{
			Debug.Assert(AcceptableOps.Contains(opc));

			this.m_arraytype = arraytype;
			this.m_reftype = readthroughtype;
			this.m_readfield = readfield;
		}

		public static IRLoadToEvalStackIndirect MakeArrayLengthOperation(IRSourceLocation sloc)
		{
			return new IRLoadToEvalStackIndirect(OpCode.Op.ArrayLength, null, null, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadArrayOperation(IRArrayType arraytype, IRSourceLocation sloc)
		{
			Debug.Assert(arraytype != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.ArrayLoad, arraytype, null, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadIndirectOperation(IRReferenceType throughreftype, IRSourceLocation sloc)
		{
			Debug.Assert(throughreftype != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.IndirectRead, null, throughreftype, null, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadStructFieldOperation(IRMemberField readfield, IRSourceLocation sloc)
		{
			Debug.Assert(readfield != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.LoadStructField, null, null, readfield, sloc);
		}

		public static IRLoadToEvalStackIndirect MakeLoadClassFieldOperation(IRMemberField readfield, IRSourceLocation sloc)
		{
			Debug.Assert(readfield != null);

			return new IRLoadToEvalStackIndirect(OpCode.Op.LoadMemberField, null, null, readfield, sloc);
		}

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder();
			if(this.OperationCode == OpCode.Op.ArrayLength)
				sb.Append("lenarr:  ");
			else if(this.OperationCode == OpCode.Op.ArrayLoad)
				sb.Append("ldarray: " + this.m_arraytype.ToString());
			else if(this.OperationCode == OpCode.Op.IndirectRead)
				sb.Append("ldind:   " + this.m_reftype.ToString());
			else if(this.OperationCode == OpCode.Op.LoadStructField)
				sb.Append("lsfld:   " + this.m_readfield.ToString());
			else if(this.OperationCode == OpCode.Op.LoadMemberField)
				sb.Append("ldfld:   " + this.m_readfield.ToString());
			else
				sb.Append("--BAD OPCODE--");

			return sb.ToString();
		}

		/// <summary>
		/// This is the array type we are going to load from.
		/// </summary>
		public IRArrayType ArrayLoadType
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.ArrayLoad);
				return this.m_arraytype;
			}
		}

		/// <summary>
		/// This is the ptr type we are going to read through.
		/// </summary>
		public IRReferenceType ThroughRefType
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.IndirectRead);
				return this.m_reftype;
			}
		}

		/// <summary>
		/// This is the struct field we are going to load.
		/// </summary>
		public IRMemberField LoadField
		{
			get
			{
				Debug.Assert((this.OperationCode == OpCode.Op.LoadStructField) | (this.OperationCode == OpCode.Op.LoadMemberField));
				return this.m_readfield;
			}
		}
	}
}

